/***************************************************************************\
 * The Mattise Kernel														*
 * Copyright 2007 Matthew Iselin											*
 * Licensed under the GPL													*
 *																			*
 * irq.cc																	*
 *																			*
 * IRQ installers and handlers.												*
 *																			*
\***************************************************************************/

#include <system.h>
#include <iostream>
#include <task.h>
#include <stdio.h>
#include <syscall.h>
// #include <LinkedList.hpp>

/// IRQ stubs must not be mangled
extern "C" {

	/// IRQ stub - IRQ0
	extern void irq0();

	/// IRQ stub - IRQ1
	extern void irq1();

	/// IRQ stub - IRQ2
	extern void irq2();

	/// IRQ stub - IRQ3
	extern void irq3();

	/// IRQ stub - IRQ4
	extern void irq4();

	/// IRQ stub - IRQ5
	extern void irq5();

	/// IRQ stub - IRQ6
	extern void irq6();

	/// IRQ stub - IRQ7
	extern void irq7();

	/// IRQ stub - IRQ8
	extern void irq8();

	/// IRQ stub - IRQ9
	extern void irq9();

	/// IRQ stub - IRQ10
	extern void irq10();

	/// IRQ stub - IRQ11
	extern void irq11();

	/// IRQ stub - IRQ12
	extern void irq12();

	/// IRQ stub - IRQ13
	extern void irq13();

	/// IRQ stub - IRQ14
	extern void irq14();

	/// IRQ stub - IRQ15
	extern void irq15();

	/// The IRQ handler
	void irq_handler(struct regs *r);
};

/// IRQ handler type
typedef void (*irq_handler_proc)(struct regs*);

/// Function pointers for each IRQ
irq_handler_proc irq_routines[16] =
{
	0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0
};

// IRQ redirection to a userland program
struct irq_redir {
	int pid; /// PID of the userland process
	int irqnum; /// IRQ number
};

/// Redirection for each IRQ to specific drivers
CLinkedList<struct irq_redir> irqRedirection;

/// Installs a custom IRQ handler for a certain IRQ
void irq_install_handler(int irq, void (*handler)(struct regs *r))
{
	irq_routines[irq] = handler;
}

/// Installs a userland handler for an IRQ
void irq_install_userland_handler( int irq, int handlerid )
{
	// add it to the list
	struct irq_redir tmp = { handlerid, irq };
	irqRedirection.Push( tmp );
}

/// Uninstalls an IRQ handler
void irq_uninstall_handler(int irq)
{
	irq_routines[irq] = 0;
}

/// Removes an IRQ handler for a process
void irq_remove_userland_handler( int irq, int pid )
{
	// find the PID
	for( int i = 0; i < irqRedirection.Size(); i++ )
	{
		struct irq_redir tmp = irqRedirection.Get( i );
		if( tmp.irqnum == irq && tmp.pid == pid )
		{
			irqRedirection.Delete( i );
			break;
		}
	}
}

/// Remaps the PIC to make the IRQs fill spaces 32-47 in the IDT
void irq_remap(void)
{
	outportb(0x20, 0x11);
	outportb(0xA0, 0x11);
	outportb(0x21, 0x20);
	outportb(0xA1, 0x28);
	outportb(0x21, 0x04);
	outportb(0xA1, 0x02);
	outportb(0x21, 0x01);
	outportb(0xA1, 0x01);
	outportb(0x21, 0x0);
	outportb(0xA1, 0x0);
}

/// Installs the ISRs for the IRQs
void irq_install()
{
	irq_remap();

	idt_set_gate(32, (unsigned)irq0, 0x08, 0x8E);
	idt_set_gate(33, (unsigned)irq1, 0x08, 0x8E);
	idt_set_gate(34, (unsigned)irq2, 0x08, 0x8E);
	idt_set_gate(35, (unsigned)irq3, 0x08, 0x8E);
	idt_set_gate(36, (unsigned)irq4, 0x08, 0x8E);
	idt_set_gate(37, (unsigned)irq5, 0x08, 0x8E);
	idt_set_gate(38, (unsigned)irq6, 0x08, 0x8E);
	idt_set_gate(39, (unsigned)irq7, 0x08, 0x8E);
	idt_set_gate(40, (unsigned)irq8, 0x08, 0x8E);
	idt_set_gate(41, (unsigned)irq9, 0x08, 0x8E);
	idt_set_gate(42, (unsigned)irq10, 0x08, 0x8E);
	idt_set_gate(43, (unsigned)irq11, 0x08, 0x8E);
	idt_set_gate(44, (unsigned)irq12, 0x08, 0x8E);
	idt_set_gate(45, (unsigned)irq13, 0x08, 0x8E);
	idt_set_gate(46, (unsigned)irq14, 0x08, 0x8E);
	idt_set_gate(47, (unsigned)irq15, 0x08, 0x8E);
}

/// Handles IRQs
void irq_handler(struct regs *r)
{
	/* This is a blank function pointer */
	void (*handler)(struct regs *r);

	/* Find out if we have a custom handler to run for this
	*  IRQ, and then finally, run it */
	kprintf( "irq%d\n", r->int_no - 32 );
	handler = irq_routines[r->int_no - 32];
	if (handler)
	{
		handler(r);
	}
	
	// send a notification to any programs who need it
	for( int i = 0; i < irqRedirection.Size(); i++ )
	{
		struct irq_redir tmp = irqRedirection.Get( i );
		if( tmp.irqnum == (r->int_no - 32) )
		{
			struct notification {
				int msgcode;
				int irqnum;
			} *myirqnot = (struct notification*) umalloc( sizeof( struct notification ) );
			myirqnot->msgcode = 0x100;
			myirqnot->irqnum = tmp.irqnum;

			struct regs m;
			m.eax = 27;
			m.ebx = tmp.pid;
			m.ecx = 0x100;
			m.edx = sizeof( struct notification );
			m.esi = (unsigned int) myirqnot;
			SysCall( &m ); // notify the process
		}
	}

	/* If the IDT entry that was invoked was greater than 40
	*  (meaning IRQ8 - 15), then we need to send an EOI to
	*  the slave controller */
	if (r->int_no >= 40)
	{
		outportb(0xA0, 0x20);
	}

	/* In either case, we need to send an EOI to the master
	*  interrupt controller too */
	outportb(0x20, 0x20);
}
